fix: add 30-second grace period for disconnects in 1v1 games (BUG-05)#3945
fix: add 30-second grace period for disconnects in 1v1 games (BUG-05)#3945berkelmali wants to merge 2 commits into
Conversation
Previously, a momentary WiFi blip would instantly declare the opponent as winner in 1v1 ranked games. WinCheckExecution checked isDisconnected() every ~1 second, with no reconnect window. Changes: - Track disconnectGraceTick when only 1 of 2 humans is connected - Winner declared only after 300 ticks (30 seconds) elapse - Grace timer resets if the disconnected player reconnects - Both-disconnected edge case handled (most tiles wins after grace) 4 new tests cover: immediate, expired, reconnect, both-disconnect
WalkthroughWinCheckExecution replaces immediate winner declaration in 1v1 ranked mode with a 300-tick disconnect grace period. When one human disconnects the timer starts and the win is awarded only after the grace elapses; reconnection resets the timer. Tests exercise start, expiration, reconnect reset, both-disconnect, and single-human edge cases. Changes1v1 Disconnect Grace Period
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/core/executions/WinCheckExecution.test.ts (1)
572-603: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd a post-grace assertion for simultaneous disconnects.
This test only checks “not immediate winner.” Please also advance beyond grace and assert winner-by-tiles, so this contract is locked by tests.
Suggested test extension
test("should not set winner immediately when both players disconnect", async () => { @@ const human1 = game.player("Player1"); const human2 = game.player("Player2"); + // Make winner deterministic for post-grace check + let assigned = 0; + game.map().forEachTile((tile) => { + if (!game.map().isLand(tile)) return; + if (assigned < 1) { + human1.conquer(tile); + assigned++; + } + }); + human1.markDisconnected(true); human2.markDisconnected(true); @@ winCheck.checkWinnerFFA(); // No grace timer was started (both disconnected simultaneously) expect(setWinnerSpy).not.toHaveBeenCalled(); expect(winCheck.isActive()).toBe(true); + + game.endSpawnPhase(); + for (let i = 0; i < 310; i++) { + game.executeNextTick(); + } + winCheck.checkWinnerFFA(); + expect(setWinnerSpy).toHaveBeenCalledWith(human1, expect.anything()); + expect(winCheck.isActive()).toBe(false); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/core/executions/WinCheckExecution.test.ts` around lines 572 - 603, Extend the test to also verify the post-grace outcome: after the initial assertions, advance the test timer/passage of time beyond the grace period (or call the WinCheckExecution method that processes end-of-grace), then invoke the win-checking logic again (e.g., call winCheck.checkWinnerFFA() or the appropriate tick/resolve method on WinCheckExecution) and assert that game.setWinner (setWinnerSpy) is called with the expected winner reason (winner-by-tiles) and that winCheck.isActive() is false; reference WinCheckExecution, winCheck.checkWinnerFFA(), winCheck.isActive(), and the setWinnerSpy assigned to game.setWinner when adding these steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/core/execution/WinCheckExecution.ts`:
- Around line 87-103: When both players are disconnected (connectedHumans.length
=== 0 && allHumans.length === 2) the code returns without initializing the grace
timer if this.disconnectGraceTick is null; update the branch to set
this.disconnectGraceTick = this.mg.ticks() when disconnectGraceTick is null so
the DISCONNECT_GRACE_TICKS countdown begins, then return; keep the existing
logic that checks elapsed >= WinCheckExecution.DISCONNECT_GRACE_TICKS to pick
the winner (using allHumans[0]) and call this.mg.setWinner(...) when grace
expires.
---
Outside diff comments:
In `@tests/core/executions/WinCheckExecution.test.ts`:
- Around line 572-603: Extend the test to also verify the post-grace outcome:
after the initial assertions, advance the test timer/passage of time beyond the
grace period (or call the WinCheckExecution method that processes end-of-grace),
then invoke the win-checking logic again (e.g., call winCheck.checkWinnerFFA()
or the appropriate tick/resolve method on WinCheckExecution) and assert that
game.setWinner (setWinnerSpy) is called with the expected winner reason
(winner-by-tiles) and that winCheck.isActive() is false; reference
WinCheckExecution, winCheck.checkWinnerFFA(), winCheck.isActive(), and the
setWinnerSpy assigned to game.setWinner when adding these steps.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8de81f51-5127-48e1-b85b-099443c325aa
📒 Files selected for processing (2)
src/core/execution/WinCheckExecution.tstests/core/executions/WinCheckExecution.test.ts
…nnect test (BUG-05)
Resolves #4093
Description:
In 1v1 ranked games, any disconnection immediately terminated the match and awarded the win to the still-connected player. This punished legitimate players for transient ISP drops and allowed losing players to exploit ragequit-denial by intentionally killing their connection to invalidate a clean win.
This PR adds a 300-tick (30-second) grace period state machine to
WinCheckExecution. The match is held open after a disconnect; if the player reconnects within the window the timer resets and play resumes. If the grace period expires the opponent wins. If both players disconnect, the player with more tiles at first-disconnect time wins (deterministic tiebreaker). Bots and nations are excluded from the 1v1 human-only logic.Please complete the following:
Please put your Discord username so you can be contacted if a bug or regression is found:
barfires